#include "W25Qxxx.h"
#include "spi.h"

#define CMD_Write_Enable (0x06)   // 写功能打开
#define CMD_Write_Disable (0x04)  // 写功能关闭
#define CMD_ID (0x90)             // 读ID
#define CMD_ReadStatusReg1 (0x05) // 读取状态寄存器
#define CMD_Placeholder (0xFF)    // 占位符
#define CMD_Sector_Erase (0x20)   // 扇区擦除
#define CMD_Read_Data (0x03)      // 读取数据
#define CMD_Write_Page (0x02)     // 写页

uint8_t Flash_Buf[16][256] = {0};

#if defined(USE_W25Q16)
uint8_t Flash_Address_Len = 24;
#elif defined(USE_W25Q32)
uint8_t Flash_Address_Len = 24;
#elif defined(USE_W25Q64)
uint8_t Flash_Address_Len = 24;
#elif defined(USE_W25Q128)
uint8_t Flash_Address_Len = 24;
#elif defined(USE_W25Q256)
uint8_t Flash_Address_Len = 32;
#elif defined(USE_W25Q512)
uint8_t Flash_Address_Len = 32;
#else
#error "Prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

// 设置片选(CS)为低电平选中
__STATIC_FORCEINLINE void W25Qxxx_CS_Low(void)
{
    HAL_GPIO_WritePin(Flash_CS_GPIO_Port, Flash_CS_Pin, GPIO_PIN_RESET);
}

// 设置片选(CS)为高电平未选中
__STATIC_FORCEINLINE void W25Qxxx_CS_Hight(void)
{
    HAL_GPIO_WritePin(Flash_CS_GPIO_Port, Flash_CS_Pin, GPIO_PIN_SET);
}

/**
 * @brief 通过SPI发送接收数据（阻塞）（SPI是移位发送，接收时要发送数据，发送时也会收到数据）
 * @param TxData:发送的数据
 * @retval 接收的数据
 */
static uint8_t W25Qxxx_SPI_RW_Byte(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&Flash_SPI_Handle, &TxData, &Rxdata, 1, 1000);
    return Rxdata;
}

/**
 * @brief 写保护
 * @param Functional:
 * @arg 1:允许写入
 * @arg 0:不允许写入
 */
static void W25Qxxx_Write_Protect(uint8_t Functional)
{
    W25Qxxx_CS_Low();
    if (Functional == 0)
        W25Qxxx_SPI_RW_Byte(CMD_Write_Disable); // 不允许写入
    else if (Functional == 1)
        W25Qxxx_SPI_RW_Byte(CMD_Write_Enable); // 允许写入
    W25Qxxx_CS_Hight();
}

// 读取寄存器1的状态
static uint8_t W25Qxxx_Read_StatusReg1(void)
{
    uint8_t state = 0;
    W25Qxxx_CS_Low();
    W25Qxxx_SPI_RW_Byte(CMD_ReadStatusReg1);      // 发送命令
    state = W25Qxxx_SPI_RW_Byte(CMD_Placeholder); // 接收数据
    W25Qxxx_CS_Hight();
    return state;
}

// 读取芯片的ID
uint16_t W25Qxxx_Read_ID(void)
{
    uint32_t zj1, zj2;
    W25Qxxx_CS_Low();
    W25Qxxx_SPI_RW_Byte(CMD_ID);                // 发送命令
    W25Qxxx_SPI_RW_Byte(CMD_Placeholder);       // 占位符
    W25Qxxx_SPI_RW_Byte(CMD_Placeholder);       // 占位符
    W25Qxxx_SPI_RW_Byte(0x00);                  // 必须为0
    zj1 = W25Qxxx_SPI_RW_Byte(CMD_Placeholder); // 发送占位符读取数据
    zj2 = W25Qxxx_SPI_RW_Byte(CMD_Placeholder); // 发送占位符读取数据
    W25Qxxx_CS_Hight();
    return ((zj1 << 8) | zj2);
}

// 擦除指定地址的扇区
void W25Qxxx_Sector_Erase(uint32_t Address)
{
    Address &= 0xFFFFF000;
    W25Qxxx_Write_Protect(1); // 允许写入

    W25Qxxx_CS_Low();
    W25Qxxx_SPI_RW_Byte(CMD_Sector_Erase); // 命令

    if (Flash_Address_Len == 32)
        W25Qxxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址
    W25Qxxx_CS_Hight();

    W25Qxxx_Write_Protect(0); // 关闭写入
    // while (W25Qxxx_Read_StatusReg1() & 0x01) // 等待写完
    //     ;
}

// 等待写入/擦除完成
void W25Qxxx_Wait_Free(void)
{
    while (W25Qxxx_Read_StatusReg1() & 0x01) // 等待写完
        continue;
}

/**
 * @brief 读取数据
 * @param Address:要读取的地址
 * @param Buf:将数据放入的数组地址
 * @param Len:读取的字节数
 */
void W25Qxxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxxx_CS_Low();
    W25Qxxx_SPI_RW_Byte(CMD_Read_Data); // 命令

    if (Flash_Address_Len == 32)
        W25Qxxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址
    for (int i = 0; i < Len; i++)
        Buf[i] = W25Qxxx_SPI_RW_Byte(CMD_Placeholder);
    W25Qxxx_CS_Hight();
}

/**
 * @brief 按页写入数据
 * @param Address:要写入的地址的首地址（必须首地址）
 * @param Buf:将数据的数组地址
 * @param Len:写入的字节数
 */
static void W25Qxxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxxx_Write_Protect(1); // 允许写入
    W25Qxxx_Wait_Free();
    W25Qxxx_CS_Low();
    W25Qxxx_SPI_RW_Byte(CMD_Write_Page); // 命令

    if (Flash_Address_Len == 32)
        W25Qxxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxxx_SPI_RW_Byte((Address & 0x00000000) >> 0);  // 地址(确保是首地址)
    for (int i = 0; i < 256; i++)
    {
        if (i < Len)
            W25Qxxx_SPI_RW_Byte(Buf[i]);
        else
            W25Qxxx_SPI_RW_Byte(0xFF);
    }
    W25Qxxx_CS_Hight();
    W25Qxxx_Write_Protect(0); // 关闭写入
}

/**
 * @brief 先擦除再向扇区写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 */
static void W25Qxxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;

    for (uint32_t i = 0; i < 16; i++) // 读取原来数据到缓冲区
        W25Qxxx_Read_Data((Add_Bef & 0xFFFFF000) | (i << 8), Flash_Buf[i], 256);

    for (uint32_t i = Add_Bef; i < Add_Aft; i++) // 向缓冲区写入数据
    {
        Flash_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];
        Num++;
    }

    W25Qxxx_Sector_Erase(Add_Bef); // 清空这个扇区
    W25Qxxx_Wait_Free();

    for (uint32_t i = 0; i < 16; i++) // 向W25Qxxx写入缓冲区内的数据
    {
        W25Qxxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), Flash_Buf[i], 256);
        W25Qxxx_Wait_Free();
    }
}

/**
 * @brief 先擦除再写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 */
void W25Qxxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;
    if (Len < 4096)
    {
        W25Qxxx_Write_Sector(Add_Bef, Buf, Len);
    }
    else
    {
        W25Qxxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef));
        Num = ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef);
        for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000; i < (Add_Aft & 0xFFFFF000); i += 0x00001000)
        {
            W25Qxxx_Write_Sector(i, &Buf[Num], 4096);
            Num += 4096;
        }
        W25Qxxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000));
    }
}
